package org.gap.jseed.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * Used to help in identifying the class/interface with the method that is annotated,
 * and returning the annotation.
 * 
 * @author gpelcha
 *
 */
public class AnnotationExtractor {
	private final Class<? extends Object> concreteClass;
	private String methodName;
	private Class<?>[] methodParameterTypes;

	/**
	 * Finds the annotation definition (since it will may not exist on the {@code instance} 
	 * declaring class).
	 * 
	 * @param <T> the annotation 
	 * @param instance the subject to look on
	 * @param overridingMethod the overriding method of the annotated method
	 * @param annotation the specific annotation type
	 * @return the annotation on the interface or superclass
	 * @throws NoSuchMethodException should not occur if the InvocationHandler
	 * is tied to the right annotation.
	 */
	public static <T extends Annotation> T getAnnotation(Object instance, Method overridingMethod, Class<T> annotation)
			throws NoSuchMethodException {
		AnnotationExtractor annotationExtractor = new AnnotationExtractor(instance.getClass(), overridingMethod);
		return annotationExtractor.extract(annotation);
	}
	
	public AnnotationExtractor(Class<? extends Object> concreteClass, Method methodOverridingAnnotated) {
		this.concreteClass = concreteClass;
		methodName = methodOverridingAnnotated.getName();
		methodParameterTypes = methodOverridingAnnotated.getParameterTypes();
	}

	private <T extends Annotation> T extract(Class<T> annotation) 
			throws NoSuchMethodException {
		Class<?> superClass = concreteClass.getSuperclass();
		if (isAnnotationOnMethod(superClass, annotation)) 
			return getAnnotationFromClass(superClass, annotation);
		return extractAnnotationFromInterface(annotation);
	}
	
	private <T extends Annotation> T extractAnnotationFromInterface(Class<T> annotation) {
		for (Class<?> each : concreteClass.getInterfaces()) {
			if (isAnnotationOnMethod(each, annotation)) {
				return getAnnotationFromClass(each, annotation);
			}
		}
		return null;
	}
	
	private <T extends Annotation> T getAnnotationFromClass(Class<?> classWithAnnotation, Class<T> annotation) {
		return getMethodWithAnnotation(classWithAnnotation).getAnnotation(annotation);
	}

	private Method getMethodWithAnnotation(Class<?> classWithAnnotation) {
		try {
			return classWithAnnotation.getMethod(methodName, methodParameterTypes);
		} catch (NoSuchMethodException e) {
			return null;
		}
	}
	
	private <T extends Annotation> boolean isAnnotationOnMethod(Class<?> methodOnClass, Class<T> annotation) {
		try {
			methodOnClass.getMethod(methodName, methodParameterTypes).getAnnotation(annotation);
			// Get annotation on method will throw exception if it does not exist (not friendly).
			return true;
		} catch (NoSuchMethodException nsme) {
			return false;
		}
	}
}
